package org.bleep.util;

import org.bleep.resources.ResourceAnchor;

import java.net.URL;
import java.awt.Graphics;
import java.awt.color.ColorSpace;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.ComponentColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferByte;
import java.awt.image.Raster;
import java.awt.image.WritableRaster;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.HashMap;
import java.util.Hashtable;

import javax.imageio.ImageIO;

import org.lwjgl.opengl.glu.*;
import org.lwjgl.opengl.*;
import org.lwjgl.BufferUtils;

import java.nio.IntBuffer;

// Totally stolen from http://wiki.java.net/bin/view/Games/TextureLoadingExample
public class TextureManager {
    private static HashMap table = new HashMap();

    private static ColorModel glAlphaColorModel = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB), new int[] {8,8,8,8}, true, false, ComponentColorModel.TRANSLUCENT, DataBuffer.TYPE_BYTE);

    private static ColorModel glColorModel = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB), new int[] {8,8,8,0}, false, false, ComponentColorModel.OPAQUE, DataBuffer.TYPE_BYTE);

    public static int createTextureID() 
    { 
       IntBuffer buff = BufferUtils.createIntBuffer(1);
       GL11.glGenTextures(buff);
       return buff.get(0);
    }

	public static Texture getUnfilteredTexture(String name, String resourceName) throws IOException {
		Texture tex = (Texture) table.get(resourceName);

        if (tex != null) {
            return tex;
        }

        tex = getTexture(name,resourceName,
                         GL11.GL_TEXTURE_2D, // target
                         GL11.GL_RGBA,     // dst pixel format
                         GL11.GL_NEAREST, // min filter (unused)
                         GL11.GL_NEAREST, // mag filter (unused)
                         true,  // wrap?
                         false); // mipmap?

        table.put(resourceName,tex);

        return tex;
	}

    public static Texture getTexture(String name,String resourceName) throws IOException {
        Texture tex = (Texture) table.get(resourceName);

        if (tex != null) {
            return tex;
        }

        tex = getTexture(name,resourceName,
                         GL11.GL_TEXTURE_2D, // target
                         GL11.GL_RGBA,     // dst pixel format
                         GL11.GL_LINEAR, // min filter (unused)
                         GL11.GL_LINEAR, // mag filter (unused)
                         true,  // wrap?
                         false); // mipmap?

        table.put(resourceName,tex);

        return tex;
    }

    public static Texture getTexture(String name, 
                              String resourceName, 
                              int target, 
                              int dstPixelFormat, 
                              int minFilter, 
                              int magFilter, 
                              boolean wrap, 
                              boolean mipmapped) throws IOException 
    { 
        int srcPixelFormat = 0;

        // create the texture ID for this texture 
        int textureID = createTextureID(); 
        System.out.println("Loading texture "+resourceName+" to ID: "+textureID);
        Texture texture = new Texture(resourceName,target,textureID); 

        // bind this texture 
        GL11.glBindTexture(target, textureID); 

        BufferedImage bufferedImage = loadImage(resourceName); 

        // Getting the real Width/Height of the Texture in the Memory
        int realWidth = get2Fold(bufferedImage.getWidth()); 
        int realHeight = get2Fold(bufferedImage.getHeight());


        // don't need it?
        texture.setBufferedImage(bufferedImage); 
        texture.setWidth(realWidth);
        texture.setHeight(realHeight);

        if (bufferedImage.getColorModel().hasAlpha()) {
            srcPixelFormat = GL11.GL_RGBA;
        } else {
            srcPixelFormat = GL11.GL_RGB;
        }

        // convert that image into a byte buffer of texture data 
        ByteBuffer textureBuffer = convertImageData(bufferedImage,texture); 
		textureBuffer.flip();
        int wrapMode = wrap ? GL11.GL_REPEAT : GL11.GL_CLAMP; 

        if (target == GL11.GL_TEXTURE_2D) 
        { 
              GL11.glTexParameteri(target, GL11.GL_TEXTURE_WRAP_S, wrapMode); 
              GL11.glTexParameteri(target, GL11.GL_TEXTURE_WRAP_T, wrapMode); 
              GL11.glTexParameteri(target, GL11.GL_TEXTURE_MIN_FILTER, minFilter); 
              GL11.glTexParameteri(target, GL11.GL_TEXTURE_MAG_FILTER, magFilter);
        }

        // create either a series of mipmaps of a single texture image based on what's loaded 
        if (mipmapped) 
        { 
            GLU.gluBuild2DMipmaps(target, 
                                  dstPixelFormat, 
                                  realWidth, 
                                  realHeight, 
                                  srcPixelFormat, 
                                  GL11.GL_UNSIGNED_BYTE, 
                                  textureBuffer); 
        } 
        else 
        { 
            GL11.glTexImage2D(target, 
                          0, 
                          dstPixelFormat, 
                          realWidth, 
                          realHeight, 
                          0, 
                          srcPixelFormat, 
                          GL11.GL_UNSIGNED_BYTE, 
                          textureBuffer ); 
        } 

        return texture; 
    } 

   private static int get2Fold(int fold) {
      int ret = 2;
      while (ret < fold) {
         ret *= 2;
      }
      return ret;
   }

    private static ByteBuffer convertImageData(BufferedImage bufferedImage,Texture texture) throws IOException 
    { 
        ByteBuffer imageBuffer = null; 
        WritableRaster raster;
        BufferedImage texImage;

        int texWidth = get2Fold(bufferedImage.getWidth());
        int texHeight = get2Fold(bufferedImage.getHeight());

        texture.setTextureHeight(texHeight);
        texture.setTextureWidth(texWidth);

        if (bufferedImage.getColorModel().hasAlpha()) {
            raster = Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE,texWidth,texHeight,4,null);
            texImage = new BufferedImage(glAlphaColorModel,raster,false,new Hashtable());
        } else {
            raster = Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE,texWidth,texHeight,3,null);
            texImage = new BufferedImage(glColorModel,raster,false,new Hashtable());
        }

        Graphics g = texImage.getGraphics();
        g.drawImage(bufferedImage,0,0,null);

        byte[] data = ((DataBufferByte) texImage.getRaster().getDataBuffer()).getData(); 

        imageBuffer = ByteBuffer.allocateDirect(data.length); 
        imageBuffer.order(ByteOrder.nativeOrder()); 
        imageBuffer.put(data, 0, data.length); 

        return imageBuffer; 
    } 

    private static BufferedImage loadImage(String ref) throws IOException 
    { 
        URL url = ResourceAnchor.class.getClassLoader().getResource(ref);

        if (url == null) {
            throw new IOException("Cannot find: "+ref);
        }

        BufferedImage bufferedImage = ImageIO.read(new BufferedInputStream(ResourceAnchor.class.getClassLoader().getResourceAsStream(ref))); 

        return bufferedImage;
    }
}